﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Jerry.DroidBuilder.Common;
using Jerry.DroidBuilder.Controls;
using Jerry.DroidBuilder.Helpers;
using Jerry.DroidBuilder.Properties;
using System.Configuration;

namespace Jerry.DroidBuilder
{
    public class RomDirectoryViewModel : ViewModelBase
    {
        public bool _isApktoolReady;

        public RomDirectoryViewModel(string directory)
            : base(true, true, true, true)
        {
            CurrentDirectory = directory;

            SourceItems = new ObservableCollection<RomItem>();

            BuildCommand = new DelegateCommand<string>(OnBuildCommand);
            SignAllCommand = new DelegateCommand<string>(OnSignAllCommand);
            DeodexCommand = new DelegateCommand<string>(OnDeodexCommand);
            DexoptCommand = new DelegateCommand<string>(OnDexoptCommand);
            ZipAlignCommand = new DelegateCommand<string>(OnZipAlignCommand);
            ExpandItemCommand = new DelegateCommand<RomItem>(OnExpandItemCommand);
            ContextMenuOpeningCommand = new DelegateCommand<ContextMenuEventArgs>(OnContextMenuOpeningCommand);

            Initialize();
        }

        public ICommand BuildCommand { get; private set; }
        public ICommand SignAllCommand { get; private set; }
        public ICommand OpenItemCommand { get; set; }
        public ICommand DeodexCommand { get; private set; }
        public ICommand DexoptCommand { get; private set; }
        public ICommand ZipAlignCommand { get; private set; }
        public ICommand ExpandItemCommand { get; private set; }
        public ICommand ContextMenuOpeningCommand { get; private set; }

        public string CurrentDirectory { get; private set; }
        public string StatusInfo { get; private set; }
        public ObservableCollection<string> Directories { get; private set; }
        public ObservableCollection<RomItem> SourceItems { get; private set; }

        public void Initialize()
        {
            var files = new List<string>(Directory.GetFiles(CurrentDirectory, "*", SearchOption.AllDirectories));

            SourceItems.Clear();

            ListDirectory(CurrentDirectory, SourceItems);

            // Rest apk tool ready status,
            // may need to re-install framework-res.apk for apktool.
            _isApktoolReady = false;
        }

        private void ListDirectory(string directory, Collection<RomItem> items)
        {
            var directories = Directory.GetDirectories(directory);

            foreach (var item in directories)
            {
                var subItem = new RomItem(true, item);

                ListDirectory(item, subItem.SubItems);

                items.Add(subItem);
            }

            var files = Directory.GetFiles(directory, "*");

            foreach (var item in files)
            {
                items.Add(new RomItem(false, item));
            }
        }

        #region Deodex

        private void OnDeodexCommand(string payload)
        {
            var result = MessageBox.Show(Lang.Deodex_Backup_Message_Content, Lang.Deodex_Backup_Message_Title, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);

            if (result == MessageBoxResult.Yes)
            {
                BackgroundRunner.Run(delegate
                {
                    DeodexHelper.DoDeodex(CurrentDirectory);
                });
            }
        }

        private void DoBackup()
        {
            // TODO:
        }

        #endregion

        #region Deodex

        private void OnDexoptCommand(string payload)
        {
            var result = MessageBox.Show(Lang.Dexopt_Backup_Message_Content, Lang.Dexopt_Backup_Message_Title, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);

            if (result == MessageBoxResult.Yes)
            {
                var useEmu = ConfigurationManager.AppSettings["DEXOPT_USE_EMU"] == "true";
                var remoteTemp = ConfigurationManager.AppSettings["DEXOPT_TEMP_DIR"];

                BackgroundRunner.Run(delegate
                {
                    DeodexHelper.DoDexopt(CurrentDirectory, remoteTemp, useEmu);
                });
            }
        }

        #endregion

        #region ZipAlign

        private void OnZipAlignCommand(string payload)
        {
            var result = MessageBox.Show(Lang.ZipAlign_Message_Content, Lang.ZipAlign_Message_Title, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);

            if (result == MessageBoxResult.Yes)
            {
                BackgroundRunner.Run(delegate
                {
                    var alignedCount = 0;
                    var packages = new List<string>(Directory.GetFiles(CurrentDirectory, "*.apk", SearchOption.AllDirectories));

                    foreach (var fileName in packages)
                    {
                        StatusReport.Update(Lang.ZipAlign_Process, Path.GetFileName(fileName));

                        if (Connector.ZipAlign(fileName))
                        {
                            alignedCount++;
                        }
                    }

                    StatusReport.Update(Lang.ZipAlign_Completed, alignedCount);
                });
            }
        }

        #endregion

        #region Build

        private void OnBuildCommand(string payload)
        {
            BackgroundRunner.Run(delegate
            {
                var buildName = CurrentDirectory + ".zip";
                var targetName = CurrentDirectory + "_signed.zip";

                try
                {
                    StatusReport.Update(Lang.Build_Prepare);
                    PackageHelper.Create(CurrentDirectory, buildName);

                    StatusReport.Update(Lang.Build_Sign);
                    Connector.SignApk(buildName, targetName);

                    StatusReport.Update(Lang.Build_Completed, Path.GetFileName(targetName));
                }
                catch (Exception ex)
                {
                    // TODO:
                }

                // Deletes temporary zip file.
                if (File.Exists(buildName))
                {
                    File.Delete(buildName);
                }
            });
        }

        #endregion

        #region SignAll

        private void OnSignAllCommand(string payload)
        {
            var fileNames = new List<string>(Directory.GetFiles(CurrentDirectory, "*.apk", SearchOption.AllDirectories));

            //fileNames.AddRange(Directory.GetFiles(CurrentDirectory, "*.jar", SearchOption.AllDirectories));

            foreach (var fileName in fileNames)
            {
                var tempName = fileName + ".signed";

                try
                {
                    Connector.SignApk(fileName, tempName);

                    File.Delete(fileName);
                    File.Move(tempName, fileName);
                }
                catch (Exception ex)
                {
                    // TODO:
                }
            }
        }

        #endregion

        #region Expand Item

        private void OnExpandItemCommand(RomItem item)
        {
            if (!item.IsApk && !item.IsJar)
            {
                return;
            }

            if (MessageBoxResult.No == MessageBox.Show(Lang.Expand_Message_Content, Lang.Expand_Message_Title, MessageBoxButton.YesNo))
            {
                return;
            }

            if (!_isApktoolReady)
            {
                var frameworkPath = Directory.GetFiles(CurrentDirectory, "framework.jar", SearchOption.AllDirectories);
                var frameworkDirectory = string.Empty;

                if (frameworkPath.Length > 0)
                {
                    frameworkDirectory = Path.GetDirectoryName(frameworkPath[0]);
                }

                var resources = Directory.GetFiles(frameworkDirectory, "*res*.apk");

                foreach (var fileName in resources)
                {
                    Connector.ApktoolInstall(fileName);
                }

                _isApktoolReady = true;
            }

            var outputDirectory = Path.Combine(Util.TempDirectory, item.Name);
            var result = Connector.Apktool(item.FileName, outputDirectory);

            item.OutputDirectory = outputDirectory;
            ListDirectory(outputDirectory, item.SubItems);
        }

        #endregion

        #region ContextMenuOpening

        private void OnContextMenuOpeningCommand(ContextMenuEventArgs e)
        {
            var item = Util.FindParent<TreeListViewItem>(e.OriginalSource as DependencyObject);

            if (null != item && item.DataContext is RomItem)
            {
                item.IsSelected = true;
            }
            else
            {
                e.Handled = true;
            }
        }

        #endregion

        #region Apply Patch

        internal void ApplyPatch(string patchPath)
        {
            BackgroundRunner.Run(delegate
            {
                StatusReport.Update(Lang.Patch_Extract);

                PackageHelper.Extract(patchPath, CurrentDirectory);

                var deleteFile = Path.Combine(CurrentDirectory, "file.delete");

                if (File.Exists(deleteFile))
                {
                    StatusReport.Update(Lang.Patch_DeleteFiles);
                    DeleteUselessFiles(deleteFile);
                }

                var patchFiles = Directory.GetFiles(CurrentDirectory, "*.patch", SearchOption.AllDirectories);

                foreach (var patchName in patchFiles)
                {
                    var target = patchName.Replace(".patch", string.Empty);

                    StatusReport.Update(Lang.Patch_PatchFile, Path.GetFileName(target));

                    if (File.Exists(target))
                    {
                        if (target.EndsWith("build.prop") || target.EndsWith("updater-script"))
                        {
                            ApplyTextPatch(target, patchName);
                        }
                        else
                        {
                            ApplyPackagePatch(target, patchName);
                        }
                    }

                    File.Delete(patchName);
                }

                StatusReport.Update(Lang.Patch_Completed);
            });
        }

        private void ApplyTextPatch(string target, string patchName)
        {
            const string REPLACE = "REPLACE";
            const string WITH = "WITH";
            const string REMOVE = "REMOVE";
            const string VALUE = "VALUE";

            var content = File.ReadAllText(target, Encoding.Default).Replace("\r\n", "\n");
            var patchContent = File.ReadAllText(patchName, Encoding.Default).Replace("\r\n", "\n");
            var matches = Regex.Matches(patchContent, "(REPLACE|WITH|REMOVE|VALUE)\\[(.+?)\\]", RegexOptions.Singleline);
            var updateValues = new List<KeyValuePair<string, string>>();

            foreach (Match match in matches)
            {
                updateValues.Add(new KeyValuePair<string, string>(match.Groups[1].Value, match.Groups[2].Value));
            }

            for (int i = 0; i < updateValues.Count; i++)
            {
                var update = updateValues[i];

                if (update.Key == REPLACE)
                {
                    if (i + 1 < updateValues.Count && updateValues[i + 1].Key == WITH)
                    {
                        var trimedValue = update.Value.Trim('\n');
                        var replaceValue = updateValues[i + 1].Value;

                        const string REG_BLOCK = "\\#\\{\\#(.+?)\\#\\}\\#";

                        if (Regex.IsMatch(trimedValue, REG_BLOCK))
                        {
                            var finalValue = string.Empty;
                            var blocks = Regex.Split(trimedValue, REG_BLOCK, RegexOptions.Singleline);

                            for (int num = 0; num < blocks.Length; num++)
                            {
                                finalValue += num % 2 != 0 ? blocks[num] : Regex.Escape(blocks[num]);
                            }

                            content = Regex.Replace(content, finalValue, replaceValue, RegexOptions.Singleline);
                        }
                        else
                        {
                            content = content.Replace(trimedValue, replaceValue);
                        }
                    }
                }
                else if (update.Key == REMOVE || update.Key == VALUE)
                {
                    var lines = update.Value.Split('\n');

                    foreach (var line in lines)
                    {
                        var lineValue = line.Trim();

                        if (string.IsNullOrEmpty(lineValue) || lineValue.StartsWith("#"))
                        {
                            continue;
                        }

                        var splittedValue = lineValue.Split('=');
                        var pattern = splittedValue[0].Trim() + " *=.+\n?";
                        var replacement = (update.Key == VALUE) ? lineValue + "\n" : string.Empty;

                        if (Regex.IsMatch(content, pattern, RegexOptions.IgnoreCase))
                        {
                            content = Regex.Replace(content, pattern, replacement, RegexOptions.IgnoreCase);
                        }
                        else if (update.Key == VALUE)
                        {
                            content += "\n" + lineValue;
                        }
                    }
                }
            }

            File.WriteAllText(target, content, Encoding.Default);
        }

        private void ApplyPackagePatch(string targetName, string patchName)
        {
            var tempName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
            var classesDex = Path.Combine(tempName, "classes.dex");

            PackageHelper.Extract(patchName, tempName);

            if (Directory.Exists(classesDex) || File.Exists(classesDex))
            {
                var classesDexIsDir = Directory.Exists(classesDex);

                Connector.Baksmali(targetName);

                if (classesDexIsDir)
                {
                    var files = Directory.GetFiles(classesDex, "*", SearchOption.AllDirectories);

                    foreach (var item in files)
                    {
                        var targetPart = item.Substring(classesDex.Length + 1);

                        if (item.EndsWith(".patch"))
                        {
                            var target = Path.Combine(".\\out", targetPart.Replace(".patch", string.Empty));
                            ApplyTextPatch(target, item);
                        }
                        else
                        {
                            var target = Path.Combine(".\\out", targetPart);
                            File.Copy(item, target, true);
                        }
                    }

                    //var patches = Directory.GetFiles(".\\out", "*.patch", SearchOption.AllDirectories);

                    //foreach (var item in patches)
                    //{
                    //    var target = patchName.Replace(".patch", string.Empty);
                    //    ApplyTextPatch(target, item);

                    //    File.Delete(item);
                    //}
                }
                else
                {
                    Connector.Baksmali(targetName);
                    Connector.Baksmali(classesDex, false);
                }

                try
                {
                    Connector.Smali();
                    PackageHelper.UpdateFile(targetName, Path.Combine(Util.ApplicationDirectory, "classes.dex"));
                }
                catch (Exception)
                {
                    MessageBox.Show("ERROR!!!");
                }

                if (classesDexIsDir)
                {
                    Directory.Delete(classesDex, true);
                }
                else
                {
                    File.Delete(classesDex);
                }
            }

            //if (PackageHelper.Contains(patchName, "classes.dex"))
            //{
            //    Connector.Baksmali(targetName);
            //    Connector.Baksmali(patchName, false);

            //    try
            //    {
            //        Connector.Smali();
            //        PackageHelper.UpdateFile(targetName, Path.Combine(Util.ApplicationDirectory, "classes.dex"));
            //    }
            //    catch (Exception ex)
            //    {
            //        MessageBox.Show("ERROR!!!");
            //        //Util.ShowMessage(string.Format(Lang.Deodex_Error, Path.GetFileName(item), ex.Message));
            //        //continue;
            //    }

            //    PackageHelper.Delete(patchName, "classes.dex");
            //}

            if (Directory.Exists(tempName))
            {
                PackageHelper.UpdateDirectory(targetName, tempName);
                Directory.Delete(tempName, true);
            }
        }

        private void DeleteUselessFiles(string deleteFile)
        {
            var fileNames = File.ReadAllLines(deleteFile);

            foreach (var item in fileNames)
            {
                var name = item.Trim();

                if (string.IsNullOrEmpty(name) || name.StartsWith("//"))
                {
                    continue;
                }

                var fileName = Path.Combine(CurrentDirectory, name);

                if (File.Exists(fileName))
                {
                    File.Delete(fileName);
                }
                else if (Directory.Exists(fileName))
                {
                    Directory.Delete(fileName, true);
                }
            }

            File.Delete(deleteFile);
        }

        #endregion
    }
}
